Abraxus's Blog

Trollcat no debug Write Up

Details:

Points: 500

Jeopardy style CTF

Category: Reversing

Write up:

This challenge also gave me a 64 bit executable:

file crackme

crackme: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f4599ab6673e44ee040c43849f7af66cd81a3d45, for GNU/Linux 3.2.0, stripped

After throwing it into IDA I found the main function:

__int64 __fastcall main(int a1, char **a2, char **a3)
{
  int v3; // eax
  char buf[264]; // [rsp+0h] [rbp-110h] BYREF
  ssize_t v6; // [rsp+108h] [rbp-8h]

  printf("Enter key: ");
  v6 = read(0, buf, 0xFFuLL);
  if ( v6 )
    buf[v6 - 1] = 0;
  if ( (unsigned int)sub_1738(buf, v6 - 1) )
  {
    printf("Congrats here is your flag: ");
    v3 = open("/flag", 0);
    sendfile(1, v3, 0LL, 0x100uLL);
  }
  else
  {
    puts("Invalid key");
  }
  return 0LL;
}

However, running the program in a debugger I realized that the program was not getting to this function. Running the program normally I got Invalid key when I input the wrong function however when I was in debug mode I would get Wrong password. I did a string search and found the following function:

int sub_55DD312E91E5()
{
  int v0; // eax
  char buf[32]; // [rsp+0h] [rbp-130h] BYREF
  __int64 s1[33]; // [rsp+20h] [rbp-110h] BYREF
  int v4; // [rsp+128h] [rbp-8h]
  int fd; // [rsp+12Ch] [rbp-4h]

  s1[0] = 0LL;
  ...
  s1[31] = 0LL;
  fd = open("/dev/urandom", 0);
  read(fd, buf, 0x20uLL);
  close(fd);
  v4 = 0;
  printf("Enter key: ");
  v4 = read(0, s1, 0xFFuLL);
  if ( v4 > 0 )
    *((_BYTE *)s1 + v4 - 1) = 0;
  if ( memcmp(s1, buf, 0x1FuLL) )
    return puts("Wrong password");
  puts("Congrats you are a super eleet hacker, here is your flag: ");
  v0 = open((const char *)s1, 0);
  return sendfile(1, v0, 0LL, 0x100uLL);
}

I shortened the string assignment with ... but it sinply iterates from 0-31 assigning 0LL to everything.

Following the xrefs I found a function that called this function:

__int64 sub_55DD312E941A()
{
  __int64 result; // rax

  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  alarm(0x30u);
  result = ptrace(PTRACE_TRACEME, 0LL, 0LL, 0LL);
  if ( result < 0 )
  {
    sub_55DD312E91E5();
    exit(0);
  }
  return result;
}

After rebasing the program to 0 and checking out the hex you see:

.text:000000000000148C BE 00 00 00 00    mov     esi, 0
.text:0000000000001491 BF 00 00 00 00    mov     edi, 0          ; request
.text:0000000000001496 B8 00 00 00 00    mov     eax, 0
.text:000000000000149B E8 00 FC FF FF    call    _ptrace
.text:00000000000014A0 48 85 C0          test    rax, rax
.text:00000000000014A3 79 14             jns     short loc_14B9
.text:00000000000014A5 B8 00 00 00 00    mov     eax, 0
.text:00000000000014AA E8 36 FD FF FF    call    sub_11E5
.text:00000000000014AF BF 00 00 00 00    mov     edi, 0          ; status
.text:00000000000014B4 E8 27 FC FF FF    call   

From here you just needed to NOP the instruction you didn't want, then I put the patched program into IDA and ran through the debugger again, this time it went where I wanted it to.

The function called in main was:

__int64 __fastcall sub_559D5B597738(__int64 a1, unsigned __int64 a2)
{
  int i; // [rsp+1Ch] [rbp-4h]

  sub_559D5B5974BC((__int64)aWarasg47Npgis9, 0x20uLL);
  sub_559D5B5975F8((__int64)byte_559D5B59A0D0, a2);
  for ( i = 0; a2 > i; ++i )
  {
    if ( *(_BYTE *)(i + a1) != byte_559D5B59A0D0[i] )
      return 0LL;
  }
  return 1LL;
}

Normally I would go into those functions to see what was needed but I noticed that the first function didn't depend on user input at all so I just looked a at the second function:

unsigned __int64 __fastcall sub_561B729415F8(__int64 a1, unsigned __int64 a2)
{
  __int64 v2; // kr00_8
  __int64 v3; // kr08_8
  unsigned __int64 result; // rax
  char v5; // [rsp+1Bh] [rbp-15h]
  unsigned __int64 i; // [rsp+20h] [rbp-10h]
  int v7; // [rsp+28h] [rbp-8h]
  int v8; // [rsp+2Ch] [rbp-4h]

  v8 = 0;
  v7 = 0;
  for ( i = 0LL; ; ++i )
  {
    result = i;
    if ( i >= a2 )
      break;
    v8 = (v8 + 1) % 256;
    v2 = *(char *)(qword_561B72944130 + v8) + v7;
    v7 = (unsigned __int8)(HIBYTE(v2) + *(_BYTE *)(qword_561B72944130 + v8) + v7) - HIBYTE(HIDWORD(v2));
    v5 = *(_BYTE *)(qword_561B72944130 + v8);
    *(_BYTE *)(qword_561B72944130 + v8) = *(_BYTE *)(qword_561B72944130 + v7);
    *(_BYTE *)(v7 + qword_561B72944130) = v5;
    v3 = *(char *)(qword_561B72944130 + v8) + *(char *)(qword_561B72944130 + v7);
    *(_BYTE *)(a1 + i) ^= *(_BYTE *)(qword_561B72944130
                                   + (unsigned __int8)(HIBYTE(v3)
                                                     + *(_BYTE *)(qword_561B72944130 + v8)
                                                     + *(_BYTE *)(qword_561B72944130 + v7))
                                   - HIBYTE(HIDWORD(v3)));
  }
  return result;
}

This used the length of the input to iterate through, so I just put in 32 a's and then I simply stepped to the for loop and looked at what byte_559D5B59A0D0 contained since that was being compared with the string.

Using lazy ida I got that the string was:

[0x64, 0x75, 0x6D, 0x62, 0x68, 0x61, 0x63, 0x6B, 0x65, 0x72, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x00]

I then made a small python script to print that out:

key = [0x64, 0x75, 0x6D, 0x62, 0x68, 0x61, 0x63, 0x6B, 0x65, 0x72, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64, 0x00]

s = ""

for i in key:  
    s += chr(i)

print(s)

This printed out:

dumbhackerpassword

I then used this key:

nc 157.230.33.195 3333

Enter key: dumbhackerpassword
Congrats here is your flag: Trollcat{y0u_ptr4c3d_m3}